package lsp

import (
	"gitee.com/big_meteor/lsp/utils"
	"math"
)

var floatSchemaMessage = map[string]string{
	"required":  "{{#label}} is not allowed to be empty",
	"equal":     "{{#label}} must be {{#target}}",
	"not":       "{{#label}} cannot be {{#target}}",
	"valid":     "{{#label}} must be one of {{#target}}",
	"invalid":   "{{#label}} cannot be one of {{#target}}",
	"greater":   "{{#label}} must be greater than {{#limit}}",
	"less":      "{{#label}} must be less than {{#limit}}",
	"max":       "{{#label}} must be less than or equal to {{#limit}}",
	"min":       "{{#label}} must be greater than or equal to {{#limit}}",
	"multiple":  "{{#label}} must be a multiple of {{#multiple}}",
	"negative":  "{{#label}} must be a negative number",
	"positive":  "{{#label}} must be a positive number",
	"precision": "{{#label}} must have no more than {{#limit}} decimal places",
}

func NewFloat() *floatSchema {
	return &floatSchema{
		rules:     map[string]func(value float64) error{},
		whitelist: make([]float64, 0),
		blacklist: make([]float64, 0),
		anySchema: anySchema{
			label:   "value",
			message: floatSchemaMessage,
		},
	}
}

type floatSchema struct {
	anySchema
	whitelist []float64
	blacklist []float64
	rules     map[string]func(value float64) error
}

func (schema *floatSchema) addRule(method string, hook func(value float64) error) *floatSchema {
	schema.rules[method] = hook
	return schema
}

// Validate a value using the schema.
func (schema *floatSchema) Validate(value float64) error {
	for _, hook := range schema.rules {
		if err := hook(value); nil != err {
			return err
		}
	}
	return nil
}

// Custom adds a custom validation function.
func (schema *floatSchema) Custom(fn func(value float64) error) *floatSchema {
	return schema.addRule("custom", fn)
}

// Label overrides the key name in error messages.
func (schema *floatSchema) Label(name string) *floatSchema {
	schema.label = name
	return schema
}

// Required requires the number value is not allowed to be 0.
func (schema *floatSchema) Required() *floatSchema {
	return schema.addRule("required", func(value float64) error {
		if 0 == value {
			return schema.error("required", nil)
		}
		return nil
	})
}

// Equal requires the number value for target.
// target - allowed target values.
func (schema *floatSchema) Equal(target float64) *floatSchema {
	return schema.addRule("equal", func(value float64) error {
		if value == target {
			return nil
		}
		return schema.error("equal", map[string]interface{}{"target": target})
	})
}

// Not required value cannot be the target value.
// target - disallow target values.
func (schema *floatSchema) Not(target float64) *floatSchema {
	return schema.addRule("not", func(value float64) error {
		if value == target {
			return schema.error("not", map[string]interface{}{"target": target})
		}
		return nil
	})
}

// Allow whitelists a value
func (schema *floatSchema) Allow(values ...float64) *floatSchema {
	schema.whitelist = append(schema.whitelist, values...)
	return schema
}

// Valid adds the provided values into the allowed whitelist and marks them as the only valid values allowed.
func (schema *floatSchema) Valid(values ...float64) *floatSchema {
	schema.Allow(values...)
	return schema.addRule("valid", func(value float64) error {
		for _, val := range schema.whitelist {
			if val == value {
				return nil
			}
		}
		return schema.error("valid", map[string]interface{}{"target": schema.whitelist})
	})
}

// Disallow blacklists a value
func (schema *floatSchema) Disallow(values ...float64) *floatSchema {
	schema.blacklist = append(schema.blacklist, values...)
	return schema
}

// Invalid adds the provided values into the allowed blacklist and marks them as the only invalid values disallow.
func (schema *floatSchema) Invalid(values ...float64) *floatSchema {
	schema.Disallow(values...)
	return schema.addRule("invalid", func(value float64) error {
		for _, val := range schema.blacklist {
			if val == value {
				return schema.error("invalid", map[string]interface{}{"target": schema.blacklist})
			}
		}
		return nil
	})
}

// Greater specifies that the value must be greater than limit.
func (schema *floatSchema) Greater(limit float64) *floatSchema {
	return schema.addRule("greater", func(value float64) error {
		if value > limit {
			return nil
		}
		return schema.error("greater", map[string]interface{}{"limit": limit})
	})
}

// Less specifies that the value must be less than limit.
func (schema *floatSchema) Less(limit float64) *floatSchema {
	return schema.addRule("less", func(value float64) error {
		if value < limit {
			return nil
		}
		return schema.error("less", map[string]interface{}{"limit": limit})
	})
}

// Max specifies the maximum value.
func (schema *floatSchema) Max(limit float64) *floatSchema {
	return schema.addRule("max", func(value float64) error {
		if value <= limit {
			return nil
		}
		return schema.error("max", map[string]interface{}{"limit": limit})
	})
}

// Min specifies the minimum value.
func (schema *floatSchema) Min(limit float64) *floatSchema {
	return schema.addRule("min", func(value float64) error {
		if value >= limit {
			return nil
		}
		return schema.error("min", map[string]interface{}{"limit": limit})
	})
}

// Multiple specifies that the value must be a multiple of base.
func (schema *floatSchema) Multiple(base float64) *floatSchema {
	return schema.addRule("multiple", func(value float64) error {
		if 0 == math.Mod(value, base) {
			return nil
		}
		return schema.error("multiple", map[string]interface{}{"multiple": base})
	})
}

// Negative requires the number to be negative.
func (schema *floatSchema) Negative() *floatSchema {
	return schema.addRule("negative", func(value float64) error {
		if 0 > value {
			return nil
		}
		return schema.error("negative", nil)
	})
}

// Positive requires the number to be positive.
func (schema *floatSchema) Positive() *floatSchema {
	return schema.addRule("positive", func(value float64) error {
		if 0 < value {
			return nil
		}
		return schema.error("positive", nil)
	})
}

// Precision specifies the maximum number of decimal places where:
// limit - the maximum number of decimal places allowed.
func (schema *floatSchema) Precision(limit int64) *floatSchema {
	return schema.addRule("precision", func(value float64) error {
		if limit >= utils.DecimalPlaces(value) {
			return nil
		}
		return schema.error("precision", map[string]interface{}{"limit": limit})
	})
}
